import { readByte8, writeByte8 } from "../lib/utils";

let byteWidth = 8;
const bpp = 4;

export const filters = [
  filterNone,
  filterSub,
  filterUp,
  filterAvg,
  filterPaeth,
];

const filterSums = [
  filterSumNone,
  filterSumSub,
  filterSumUp,
  filterSumAvg,
  filterSumPaeth,
];

export function filter(
  imageData: ArrayBuffer,
  width: number,
  height: number,
  options: any
) {
  byteWidth = width * bpp;
  let filterEnable = width * height < Math.pow(2, 22);
  if (options.forceFilter) filterEnable = true;
  let rawPos = 0,
    pxPos = 0;
  const rawData = new ArrayBuffer((byteWidth + 1) * height);
  let filterSelect = options.filterType || 0;
  for (let y = 0; y < height; y++) {
    //CPU单线程性能低下，图片过大时关闭所有过滤器
    if (filterEnable && typeof options.filterType !== "number") {
      let min = Infinity;
      for (let i = 1; i < filterSums.length; i++) {
        const sum = filterSums[i](imageData, pxPos);
        if (sum < min) {
          filterSelect = i;
          min = sum;
        }
      }
    }
    // console.log(`第${y}行使用${filterSelect}`);
    writeByte8(rawData, [filterSelect], rawPos);
    rawPos++;
    filters[filterSelect](imageData, pxPos, rawData, rawPos, byteWidth);
    rawPos += byteWidth;
    pxPos += byteWidth;
  }
  return rawData;
}

function filterNone(
  pxData: ArrayBuffer,
  pxPos: number,
  rawData: ArrayBuffer,
  rawPos: number,
  byteWidth: number
) {
  for (let x = 0; x < byteWidth; x++) {
    writeByte8(rawData, [readByte8(pxData, pxPos + x)], rawPos + x);
  }
}

function filterSub(
  pxData: ArrayBuffer,
  pxPos: number,
  rawData: ArrayBuffer,
  rawPos: number,
  byteWidth: number
) {
  for (let x = 0; x < byteWidth; x++) {
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    writeByte8(rawData, [readByte8(pxData, pxPos + x) - left], rawPos + x);
  }
}

function filterUp(
  pxData: ArrayBuffer,
  pxPos: number,
  rawData: ArrayBuffer,
  rawPos: number,
  byteWidth: number
) {
  for (let x = 0; x < byteWidth; x++) {
    const up = pxPos > 0 ? readByte8(pxData, pxPos + x - byteWidth) : 0;
    writeByte8(rawData, [readByte8(pxData, pxPos + x) - up], rawPos + x);
  }
}

function filterAvg(
  pxData: ArrayBuffer,
  pxPos: number,
  rawData: ArrayBuffer,
  rawPos: number,
  byteWidth: number
) {
  for (let x = 0; x < byteWidth; x++) {
    const up = pxPos > 0 ? readByte8(pxData, pxPos + x - byteWidth) : 0;
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    writeByte8(
      rawData,
      [readByte8(pxData, pxPos + x) - ((left + up) >> 1)],
      rawPos + x
    );
  }
}

function filterPaeth(
  pxData: ArrayBuffer,
  pxPos: number,
  rawData: ArrayBuffer,
  rawPos: number,
  byteWidth: number
) {
  for (let x = 0; x < byteWidth; x++) {
    const up = pxPos > 0 ? readByte8(pxData, pxPos + x - byteWidth) : 0;
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    const upleft =
      pxPos > 0 && x >= bpp
        ? readByte8(pxData, pxPos + x - (byteWidth + bpp))
        : 0;
    writeByte8(
      rawData,
      [readByte8(pxData, pxPos + x) - paethPredictor(left, up, upleft)],
      rawPos + x
    );
  }
  return rawData;
}

function filterSumNone(pxData: ArrayBuffer, pxPos: number) {
  let sum = 0;
  const length = pxPos + byteWidth;
  for (let i = pxPos; i < length; i++) {
    sum += Math.abs(readByte8(pxData, i));
  }
  return sum;
}

function filterSumSub(pxData: ArrayBuffer, pxPos: number) {
  let sum = 0;
  for (let x = 0; x < byteWidth; x++) {
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    const val = readByte8(pxData, pxPos + x) - left;
    sum += Math.abs(val);
  }
  return sum;
}

function filterSumAvg(pxData: ArrayBuffer, pxPos: number) {
  let sum = 0;
  for (let x = 0; x < byteWidth; x++) {
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    const up = pxPos > 0 ? readByte8(pxData, pxPos + x - byteWidth) : 0;
    const val = readByte8(pxData, pxPos + x) - ((left + up) >> 1);
    sum += Math.abs(val);
  }
  return sum;
}

function filterSumUp(pxData: ArrayBuffer, pxPos: number) {
  let sum = 0;
  const length = pxPos + byteWidth;
  for (let x = pxPos; x < length; x++) {
    const up = pxPos > 0 ? readByte8(pxData, x - byteWidth) : 0;
    const val = readByte8(pxData, x) - up;
    sum += Math.abs(val);
  }
  return sum;
}

function paethPredictor(left: number, above: number, upLeft: number) {
  const paeth = left + above - upLeft;
  const pLeft = Math.abs(paeth - left);
  const pAbove = Math.abs(paeth - above);
  const pUpLeft = Math.abs(paeth - upLeft);

  if (pLeft <= pAbove && pLeft <= pUpLeft) {
    return left;
  }
  if (pAbove <= pUpLeft) {
    return above;
  }
  return upLeft;
}

function filterSumPaeth(pxData: ArrayBuffer, pxPos: number) {
  let sum = 0;
  for (let x = 0; x < byteWidth; x++) {
    const left = x >= bpp ? readByte8(pxData, pxPos + x - bpp) : 0;
    const up = pxPos > 0 ? readByte8(pxData, pxPos + x - byteWidth) : 0;
    const upleft =
      pxPos > 0 && x >= bpp
        ? readByte8(pxData, pxPos + x - (byteWidth + bpp))
        : 0;
    const val = readByte8(pxData, pxPos + x) - paethPredictor(left, up, upleft);

    sum += Math.abs(val);
  }

  return sum;
}
